<?php

class SRDO_Object {
	// Our database name
	private $db			= null;
	// Our table name
	private $table 		= null;
	private $resultId	= null;
	private $resultCount= null;
	// Our query parameters
	private $query		= array(	'select' 		=> '*',
									'join'			=> null,
									'where'			=> null,
									'group_by'		=> null,
									'having'		=> null,
									'order_by'		=> null,
									'limit_start'	=> null,
									'limit_count'	=> null,

									'select_table'	=> null,
									'insert_object'	=> null,
									'update_object'	=> null,
									'delete_object'	=> null
									);
	private $findPairs;
		
	public function __construct() {
		//parent::__construct();
		if ( $this->table == '' ) {
			$this->table = strtolower( get_class($this) );
		}
		if ( $this->db == '' ) {
			$def = SRDO_Table::getTable( strtolower($this->table) );
			$this->db = $def['database'];
		}
		
		// Setup Joins
		$table = $this->table();

		if ( is_array($table['field']) ) {
			foreach ($table['field'] AS $k => $v) {
				if (isset($v['fk']) && 
					$table['parent'] != $v['table'] &&
					$table['table'] != $v['table'] && 
					($v['join'] == true)) {
					SRDO::debug(4, "Adding JOIN on {$this->table}.{$k} to {$v['table']}.{$v['fk']}");
					$this->joinAdd(array($k, $v['table'] . ':' . $v['fk']));
				}
			}
		}
	}
	
	// Creates JoinTable objects
	private function setupVars($vars = null ) {
		if ($vars === null) {
			$vars = $this->vars();
		} else {
			if (!is_array($vars)) {
				$vars = array($vars);
			}

			$tmp = $this->vars();
			$tmp2 = array();
			foreach ($vars AS $v) {
				if (isset($tmp[$v])) {
					$tmp2[$v] = $tmp[$v];
				}
			}
			$vars = $tmp2;
		}
		if (is_array($vars)) {
			foreach ($vars AS $k => $v) {
				$var = $v['var'];

				// Add in our joins if object != table
				if (isset($v['object'])) {
					$this->{$var} = &self::factory($v['object']);

					// Set our select table
					$this->{$var}->_query['select_table'] = $v['table'];

					$tmp 	= &self::factory($v['table']);
					$table 	= $tmp->table();
					foreach ($table['field'] AS $kk => $vv) {
						if (isset($vv['fk']) && 
							$table['parent'] != $vv['table'] && 
							$table['table'] != $vv['table'] &&
							(!isset($vv['join']) || $vv['join'] == true)) {
							$this->{$var}->joinAdd(array($kk, $vv['table'] . ':' . $vv['fk']));
						}
					}
				} else {
					$this->{$var} = &self::factory($v['table']);
				}

				// Copy our values over to our child object
				foreach ($v['map'] AS $kk => $vv) {
					$this->{$v['var']}->{$vv} = $this->{$kk};
				}

				if ($v['load']) {
					$this->{$v['var']}->find(true);
				}
			}
		}
	}

	public static function &factory($table) {
		if ( !class_exists($table) ) {
			// Do we load it/require?
			if ( isset(SRDO::$config['table'][$table]['set']['class']) ) {
				require_once( SRDO::$config['table'][$table]['set']['class'] );
			} else {
				SRDO::generateDynamicClass($table);
			}
		}
		return new $table();
	}

	/**
	 * Returns our really cool object
	 */
	public function getDataObject() {
		// Get our table definition
		if ( !($def = $this->fields()) ) {
			return false;
		}

		$oDO = new SRDO_DataObject();
		// TODO: Refactor this call
		$oDO->_setObject( get_class($this) );

		foreach ($def AS $k => $v) {
			$oDO->_setupField($k, $v['type'], $this->{$k});
		}

		// Setup other objects toooo (our vars)
		$vars = $this->vars();
		if ($vars) {
			foreach ($vars AS $k => $v) {
				if (is_object($this->{$k})) {
					$oDO->{$k} = $this->{$k}->getDataObject();
				}
			}
		}

		return $oDO;
	}

	/**
	 * Construct a get (select) query
	 */
	public function get($key = null, $value = null) {
		if ( !is_array($key) ) {
			if ($value === null) {
				$value 	= $key;
				$keys	= $this->keys();
				$key 	= array_shift($keys);
			}

			$key = array($key => $value);
		}

		$this->findPairs = $key;
		return $this->find(true);
	}

	public function find($fetch = false) {
		if ( is_array($this->findPairs) ) {
			foreach ($this->findPairs AS $k => $v) {
				$this->{$k} = $v;
			}

			if ( isset(SRDO::$config['result'][$this->resultId]) ) {
				SRDO::$config['result'][$this->resultId]->closeCursor();
				unset(SRDO::$config['result'][$this->resultId]);
				$this->resultId = null;
			}
		} else {
			$this->findPairs = $this->getFieldValues();
		}
		
		if ( ($n = $this->_select()) !== false ) {
			if ( $fetch ) {
				$this->fetch();
			}
			return $n;
		}

		return false;
	}

	/**
	 * Loop through our results updating this object's parameters (& objects)
	 */
	public function fetch() {
		if ( $this->resultId === null ) {
			SRDO::error('fetch(): No query has been run');
			return false;
		}

		if ( !is_object(SRDO::$config['result'][$this->resultId]) ) {
			return false;
		}

		$data = SRDO::$config['result'][$this->resultId]->fetch( PDO::FETCH_ASSOC );

		if ( is_array($data) ) {
			foreach ($data AS $k => $v) {
				$this->{$k} = $v;
			}
			$this->setupVars();

			return true;
		}

		SRDO::$config['result'][$this->resultId]->closeCursor();
		unset(SRDO::$config['result'][$this->resultId]);
		$this->resultId = false;
		return false;
	}

	public function fetchArray() {
		if ( $this->resultId === null ) {
			SRDO::error('fetch(): No query has been run');
			return false;
		}

		$data = SRDO::$config['result'][$this->resultId]->fetch( PDO::FETCH_ASSOC );

		if ( is_array($data) ) {
			return $data;
		}

		SRDO::$config['result'][$this->resultId]->closeCursor();
		unset(SRDO::$config['result'][$this->resultId]);
		$this->resultId = false;
		return false;
	}

	/**
	 * Return the count of our find (or false)
	 */
	public function count() {
		return $this->find();
	}

	private function getWhereClause() {
		$ret = '';
		if ( is_array($this->query['where']) ) {
			$cond	= false;
			foreach ($this->query['where'] AS $v) {
				list($where, $logic) = $v;

				if ($ret == '' && $logic != null) {
					$where .= ' ' . $logic;
					$cond = true;
				} elseif ($ret != '' && !$cond && $logic != null) {
					$where .= ' ' . $logic;
				} elseif ($ret != '' && !$cond) {
					$where = 'AND ' . $where;
				} else {
					$cond = false;
				}

				$ret .= ($ret != '' ? ' ' : '') . $where;
			}

		}
		return $ret;
	}

	public function insert() {
		return $this->_insert();
	}

	/**
	 * @access	private
	 * @returns	mixed	false on failure, int for auto_increment or sequences, true otherwise
	 */
	private function _insert() {
		$origQuery = $this->query;
		
		$fields	= $this->fields();
		
		$pairs 	= $this->getFieldPairs($create);

		if ( $pairs ) {
			$q = 'INSERT INTO ' . $this->table . ' (';
			$q .= implode(',', array_keys($pairs));
			$q .= ') VALUES (';
			$q .= implode(',', $pairs);
			$q .= ')';

			$id = $this->query($q);
			
			if ( $id !== false ) {
				// Set our id to some value somewhere... ?
				return $id;
			}

			SRDO::error('[' . $this->table . "] _insert(): query failed");
			return false;
		}

		SRDO::error('[' . $this->table . "] _insert(): field pairs were empty or false [$pairs]");
		return false;
	}

	/**
	 *
	 */
	private function _select() {
		$orig = $this->query;
		$qry = &$this->query;

		if ( $this->resultId !== null ) {
			SRDO::error("_select(): {$this->table}: This query has already been executed, please clone the object");
			//return false;
		}

		if ( !$qry['select_table'] ) {
			$qry['select_table'] = $this->table;
		}

		$q = 'SELECT ' . $qry['select'] . ' FROM ' . $qry['select_table'];

		if ( $qry['join'] ) {
			$q .= ' ' . $qry['join'];
		}

		// Go through our fieldPairs and call whereAdd() on them...
		$pairs = $this->getFieldPairs();
		if ( $pairs ) {
			foreach ($pairs AS $k => $v) {
				$where = $qry['select_table'] . '.' . $k . '=' . $v;
				$this->whereAdd($where);
			}
		}
		if ( $qry['select_table'] != $this->table ) {
			$pairs = $this->getFieldPairs(null, null, $qry['select_table']);
			if ( $pairs ) {
				foreach ( $pairs AS $k => $v ) {
					$where = $qry['select_table'] . '.' . $k . '=' . $v;
					$this->whereAdd($where);
				}
			}
		}

		if ( $qry['where'] ) {
			$q .= ' WHERE ' .  $this->getWhereClause();
		}

		if ( $qry['group_by'] ) {
			$q .= ' GROUP BY ' . $qry['group_by'] . ($qry['having'] ? ' HAVING ' . $qry['having'] : '');
		}

		if ( $qry['order_by'] ) {
			$q .= ' ORDER BY ' . $qry['order_by'];
		}

		if ( $qry['limit_start'] ) {
			$q .= ' LIMIT ' . $qry['limit_start'] . ($qry['limit_count'] ? ', ' . $qry['limit_count'] : '');
		}

		$this->query = $orig;
		$n = $this->query($q);

		SRDO::debug(5, "_select({$this->resultId}): Found [$n] Query [$q]");

		if ( $n !== false ) {
			$this->resultCount = $n;
			return $n;
		}
		$this->resultCount = null;

		return false;
	}

	public function update() {
		return $this->_update();
	}
	
	/**
	 * Update query
	 */
	private function _update() {
		$def = $this->table();

		$q = 'UPDATE ' . $this->table . ' SET';

		$fields	= $this->fields();
		// We have key/value pairs (quoted) (Get ALL fields)
		$pairs 	= $this->getFieldPairs(null, true);
		$keys	= $this->keys();
		$set 	= array();
		if ( $pairs ) {
			foreach ( $pairs AS $k => $v ) {
				$f = $fields[$k]['update'];
				if ( $f === false ) {
					continue;
				} elseif ( !in_array($k, $keys) ) {
					if ( $v == '' && $f !== null ) {
						$v = $f;
					}
					$set[] = $k . '=' . $v;
				} else {
					$this->whereAdd($k . '=' . $v);
				}
			}
		}

		if ( count($set) == 0 ) {
			SRDO::error('[' . $this->table . '] _update(): No fields available to update!');
			return false;
		}

		$q .= ' ' . implode(', ', $set);

		if ( !$this->query['where'] ) {
			SRDO::error(	'[' . $this->table . "] _update(): No WHERE condition found, " . 
							"preventing whole table update! Call whereAdd('1=1') to override");
			return false;
		}

		$q .= ' WHERE ' . $this->getWhereClause();

		$rows = $this->query($q);

		if ( $rows !== false ) {
			return $rows;
		}

		SRDO::error('[' . $this->table . '] _update(): Rows is false [' . $rows . ']');
		return false;
	}

	public function delete() {
		return $this->_delete();
	}
	
	/**
	 * Delete query
	 */
	private function _delete() {
		$pairs = $this->getKeyPairs();
		
		if ( $pairs ) {
			foreach ( $pairs AS $k => $v ) {
				$this->whereAdd($k . '=' . $v);
			}

			if ( !$this->query['where'] ) {
				SRDO::error("_delete(): No WHERE condition found, preventing whole table deletion! whereAdd('1=1') to override");
				return false;
			}

			$q = 'DELETE FROM ' . $this->table . ' WHERE ' . $this->getWhereClause();

			$rows = $this->query($q);

			if ( $rows !== false ) {
				return $rows;
			}

			SRDO::error("[{$this->table}] _delete(): Query failed, returned [$rows]");
			return false;
		}

		SRDO::error("[{$this->table}] _delete(): No pairs found");
		return false;
	}

	private function query($query) {
		$oDB = &SRDO_Database::getPDO( $this->db );
		if ( !$oDB ) {
			SRDO::error('query(): Failed to obtain DB object');
			return false;
		}
		
		$st = $oDB->prepare( $query );
		if ( !$st->execute() ) {
			$err = $st->errorInfo();
			SRDO::error('query(): ' . $err[1] . ': ' . $err[2] );
			SRDO::error('query(): QUERY [' . substr($query, 0, 1024) . ']');
			return false;
		}

		// We have a valid query now
		$this->resultId = (int)SRDO::$config['nextResultId']++;
		SRDO::$config['result'][$this->resultId] = &$st;

		$type = strtoupper(substr(trim($query), 0, 6));
		if ( $type == 'SELECT' || $type == 'UPDATE' || $type == 'DELETE' ) {
			return $st->rowCount();
		} elseif ($type == 'INSERT') {
			return $oDB->LastInsertId();
		}

		return true;
	}

	/* ----- Query Methods ----- */
	/**
	 * Add a field or statement to the SELECT portion of a query (as applies to a select query)
	 */
	public function selectAdd($select = null) {
		if ($select === null) {
			$this->query['select'] = null;
			return;
		}

		$this->query['select'] .= ($this->query['select'] ? ', ' : '' ) . $select;
	}

	/**
	 */
	public function whereAdd($where = null, $logic = null) {
		if ($where === null) {
			$this->query['where'] = null;
			return;
		}

		if (!is_array($this->query['where'])) {
			$this->query['where'] = array();
		}

		$this->query['where'][] = array($where, $logic);
	}

	/**
	 */
	public function joinAdd($table = null, $type = 'LEFT', $alias = null, $using = null) {
		if ( $table === null ) {
			$this->query['join'] = null;
			return;
		}

		if ( $using ) {
			$append = 'USING(' . $using . ')';
		}
		
		if ( is_array($table) ) {
			/**
			 * table[0] => local key, table[1] => foreign_table:foreign_key
			 */
			$lk = $table[0];
			list($table, $fk) = explode(':', $table[1]);
			$append = 'ON ' . (isset($this->query['select_table']) ? 
								$this->query['select_table']
								: 
								$this->table) 
								. ".{$lk}={$table}.{$fk}";
		}

		$join = $type . ' JOIN ' . $table . ' ' . $append;

		$this->query['join'] .= ($this->query['join'] ? ' ' : '') . $join;
	}

	/**
	 */
	public function limit($start = null, $count = null) {
		if ( $start === null ) {
			$this->query['limit_start'] = null;
			$this->query['limit_count'] = null;
			return;
		}

		$this->query['limit_start'] = $start;
		$this->query['limit_count'] = $count;
	}

	/**
	 */
	public function orderBy($order = null, $dir = null) {
		if ( $order === null ) {
			$this->query['order_by'] = null;
			return;
		}

		$this->query['order_by'] .= ($this->query['order_by'] ? ', ' : '') . $order . ($dir != null ? ' ' . $dir : '');
	}

	/**
	 */
	public function groupBy($group = null) {
		if ( $group === null ) {
			$this->query['group_by'] = null;
			return;
		}

		$this->query['group_by'] .= ($this->query['group_by'] ? ', ' : '') . $group;
	}

	/**
	 */
	public function setFieldSQL($field, $sql) {
		// Set a field to this SQL value (select, updating, deleting) for example NOW()
		// $object->setFieldSQL('datestamp', 'NOW()');
		$this->{$field} = new SRDO_SQL($sql);
	}

	/**
	 */
	public function &getDatabaseConnection() {
		// Return the database connection for this table object (_db is generally null)
		return SRDO_Database::getPDO($this->db);
	}

	/**
	 */
	public function &getResult() {
		if ($this->resultId != false) {
			return SRDO::$config['result'][$this->resultId];
		}

		return false;
	}

	// Miscellaneous pointless methods
	public function tableName() {
		return $this->table;
	}

	/* ----- << Special methods >> ----- */
	function vars($table = null) {
		if ( $table === null ) {
			$table = $this->table;
		}
		return SRDO_Database::getTableVars($this->db, $table);
	}

	/**
	 * Wrapper to return a table definition
	 */
	public function table() {
		return SRDO_Database::getTable($this->db, $this->table);
	}

	/**
	 * Wrapper to return a tables field definition
	 */
	public function fields($table = null) {
		return SRDO_Database::getTableFields($this->db, ($table ? $table : $this->table));
	}

	public function getFieldValues() {
		if ( $field === null ) {
			$fields = $this->fields($table);
			if ( !$fields ) {
				SRDO::error('[' . $this->table . ',' . $table . '] getFieldPairs(): Returning false');
				return false;
			}
			$field = array_keys($fields);
		} elseif ( !is_array($field) ) {
			$field = array($field);
		}

		$pairs = array();
		foreach ( $field AS $f ) {
			$pairs[$f] = $this->{$f};
		}

		return $pairs;
	}

	private function getFieldPairs($field = null, $all = false, $table = null) {
		if ( $field === null ) {
			$fields = $this->fields($table);
			if ( !$fields ) {
				SRDO::error('[' . $this->table . ',' . $table . '] _getFieldPairs(): Returning false');
				return false;
			}
			$field = array_keys($fields);
		} elseif ( !is_array($field) ) {
			$field = array($field);
		}

		$pairs = array();
		foreach ( $field AS $f ) {
			if ( $all || $this->{$f} != '' ) {
				// This will always passthrough the value if it can't cast it
				$pairs[$f] = SRDO_Type::prepareSQL($fields[$f]['type'], $this->{$f});
			}
		}

		return $pairs;
	}

	public function keys() {
		return SRDO_Database::getTableKeys($this->db, $this->table);
	}

	/**
	 * Returns an array of key=value pairs for the primary keys
	 */
	private function getKeyPairs($key = null) {
		if ( $key === null ) {
			$key = $this->keys();
			if (!$key) {
				return false;
			}
		} elseif ( !is_array($key) ) {
			$key = array($key);
		}

		return $this->getFieldPairs($key);
	}
	
	private function MAP($var, $param, $fk) {
		if ( isset(SRDO::$config['table'][$this->table]['var'][$var]) ) {
			SRDO::$config['table'][$this->table]['var'][$var]['map'][$param] = $fk;
		}
	}
}